{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d0c84aa5",
   "metadata": {},
   "outputs": [],
   "source": [
    "#|default_exp style"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "61bf8203",
   "metadata": {},
   "source": [
    "# Style\n",
    "\n",
    "> Fast styling for friendly CLIs."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5437d2fc",
   "metadata": {},
   "source": [
    "::: {.callout-note}\n",
    "\n",
    "Styled outputs don't show in Quarto documentation. Please use a notebook editor to correctly view this page.\n",
    "\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "059709e3",
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "# Source: https://misc.flogisoft.com/bash/tip_colors_and_formatting\n",
    "_base = 'red green yellow blue magenta cyan'\n",
    "_regular = f'black {_base} light_gray'\n",
    "_intense = 'dark_gray ' + ' '.join('light_'+o for o in _base.split()) + ' white'\n",
    "_fmt = 'bold dim italic underline blink <na> invert hidden strikethrough'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "12d1cb35",
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class StyleCode:\n",
    "    \"An escape sequence for styling terminal text.\"\n",
    "    def __init__(self, name, code, typ): self.name,self.code,self.typ = name,code,typ\n",
    "    def __str__(self): return f'\\033[{self.code}m'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "84cac46c",
   "metadata": {},
   "source": [
    "The primary building block of the `S` API."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "33671ebf",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[34mhello\u001b[39m world\n"
     ]
    }
   ],
   "source": [
    "print(str(StyleCode('blue', 34, 'fg')) + 'hello' + str(StyleCode('default', 39, 'fg')) + ' world')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8a35abf7",
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "def _mk_codes(s, start, typ, fmt=None, **kwargs):\n",
    "    d = {k:i for i,k in enumerate(s.split())} if isinstance(s, str) else s\n",
    "    res = {k if fmt is None else fmt.format(k):start+v for k,v in d.items()}\n",
    "    res.update(kwargs)\n",
    "    return {k:StyleCode(k,v,typ) for k,v in res.items()}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ff754020",
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "# Hardcode `reset_bold=22` since 21 is not always supported\n",
    "# See: https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797\n",
    "style_codes = {**_mk_codes(_regular, 30,  'fg',                default=39),\n",
    "               **_mk_codes(_intense, 90,  'fg'),\n",
    "               **_mk_codes(_regular, 40,  'bg',    '{}_bg',    default_bg=49),\n",
    "               **_mk_codes(_intense, 100, 'bg',    '{}_bg'),\n",
    "               **_mk_codes(_fmt,     1,   'fmt'),\n",
    "               **_mk_codes(_fmt,     21,  'reset', 'reset_{}', reset=0, reset_bold=22)}\n",
    "style_codes = {k:v for k,v in style_codes.items() if '<na>' not in k}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "34a19083",
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "def _reset_code(s):\n",
    "    if s.typ == 'fg':  return style_codes['default']\n",
    "    if s.typ == 'bg':  return style_codes['default_bg']\n",
    "    if s.typ == 'fmt': return style_codes['reset_'+s.name]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eb561c7e",
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "class Style:\n",
    "    \"A minimal terminal text styler.\"\n",
    "    def __init__(self, codes=None): self.codes = [] if codes is None else codes\n",
    "    def __dir__(self): return style_codes.keys()\n",
    "    def __getattr__(self, k):\n",
    "        try: return Style(self.codes+[style_codes[k]])\n",
    "        except KeyError: return super().__getattr__(k)\n",
    "    def __call__(self, obj):\n",
    "        set_ = ''.join(str(o) for o in self.codes)\n",
    "        reset = ''.join(sorted('' if o is None else str(o) for o in set(_reset_code(o) for o in self.codes)))\n",
    "        return set_ + str(obj) + reset\n",
    "    def __repr__(self):\n",
    "        nm = type(self).__name__\n",
    "        res = f'<{nm}: '\n",
    "        res += ' '.join(o.name for o in self.codes) if self.codes else 'none'\n",
    "        return res+'>'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "56848b63",
   "metadata": {},
   "source": [
    "The main way to use it is via the exported `S` object."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "73c4d89c",
   "metadata": {},
   "outputs": [],
   "source": [
    "#|exports\n",
    "S = Style()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5bc1c8bf",
   "metadata": {},
   "source": [
    "We start with an empty style:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2eb8de72",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Style: none>"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "S"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "de2fd3a8",
   "metadata": {},
   "source": [
    "Define a new style by chaining attributes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d3add7ec",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Style: blue bold underline>"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s = S.blue.bold.underline\n",
    "s"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "45578ec4",
   "metadata": {},
   "source": [
    "You can see a full list of available styles with auto-complete by typing <kbd>S</kbd> <kbd>.</kbd> <kbd>Tab</kbd>."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ed24db31",
   "metadata": {},
   "source": [
    "Apply a style by calling it with a string:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "59e5ab51",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'\\x1b[34m\\x1b[1m\\x1b[4mhello world\\x1b[22m\\x1b[24m\\x1b[39m'"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s('hello world')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4fa4b809",
   "metadata": {},
   "source": [
    "That's a raw string with the underlying escape sequences that tell the terminal how to format text. To see the styled version we have to print it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4c5a8a96",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[34m\u001b[1m\u001b[4mhello world\u001b[22m\u001b[24m\u001b[39m\n"
     ]
    }
   ],
   "source": [
    "print(s('hello world'))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9919519b",
   "metadata": {},
   "source": [
    "You can also nest styles:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "653d867f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[1m\u001b[34mkey\u001b[39m = value \u001b[22m\u001b[37m \u001b[4m# With a comment\u001b[24m\u001b[39m and unstyled text\n"
     ]
    }
   ],
   "source": [
    "print(S.bold(S.blue('key') + ' = value ') + S.light_gray(' ' + S.underline('# With a comment')) + ' and unstyled text')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ce59dc19",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[34mthis \u001b[1mis\u001b[22m a test\u001b[39m\n"
     ]
    }
   ],
   "source": [
    "print(S.blue('this '+S.bold('is')+' a test'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7c016111",
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "def _demo(name, code):\n",
    "    s = getattr(S,name)\n",
    "    print(s(f'{code.code:>3}    {name:16}'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cf21a9bd",
   "metadata": {},
   "outputs": [],
   "source": [
    "#|export\n",
    "def demo():\n",
    "    \"Demonstrate all available styles and their codes.\"\n",
    "    for k,v in style_codes.items(): _demo(k,v)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fe52f7cb",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[30m 30    black           \u001b[39m\n",
      "\u001b[31m 31    red             \u001b[39m\n",
      "\u001b[32m 32    green           \u001b[39m\n",
      "\u001b[33m 33    yellow          \u001b[39m\n",
      "\u001b[34m 34    blue            \u001b[39m\n",
      "\u001b[35m 35    magenta         \u001b[39m\n",
      "\u001b[36m 36    cyan            \u001b[39m\n",
      "\u001b[37m 37    light_gray      \u001b[39m\n",
      "\u001b[39m 39    default         \u001b[39m\n",
      "\u001b[90m 90    dark_gray       \u001b[39m\n",
      "\u001b[91m 91    light_red       \u001b[39m\n",
      "\u001b[92m 92    light_green     \u001b[39m\n",
      "\u001b[93m 93    light_yellow    \u001b[39m\n",
      "\u001b[94m 94    light_blue      \u001b[39m\n",
      "\u001b[95m 95    light_magenta   \u001b[39m\n",
      "\u001b[96m 96    light_cyan      \u001b[39m\n",
      "\u001b[97m 97    white           \u001b[39m\n",
      "\u001b[40m 40    black_bg        \u001b[49m\n",
      "\u001b[41m 41    red_bg          \u001b[49m\n",
      "\u001b[42m 42    green_bg        \u001b[49m\n",
      "\u001b[43m 43    yellow_bg       \u001b[49m\n",
      "\u001b[44m 44    blue_bg         \u001b[49m\n",
      "\u001b[45m 45    magenta_bg      \u001b[49m\n",
      "\u001b[46m 46    cyan_bg         \u001b[49m\n",
      "\u001b[47m 47    light_gray_bg   \u001b[49m\n",
      "\u001b[49m 49    default_bg      \u001b[49m\n",
      "\u001b[100m100    dark_gray_bg    \u001b[49m\n",
      "\u001b[101m101    light_red_bg    \u001b[49m\n",
      "\u001b[102m102    light_green_bg  \u001b[49m\n",
      "\u001b[103m103    light_yellow_bg \u001b[49m\n",
      "\u001b[104m104    light_blue_bg   \u001b[49m\n",
      "\u001b[105m105    light_magenta_bg\u001b[49m\n",
      "\u001b[106m106    light_cyan_bg   \u001b[49m\n",
      "\u001b[107m107    white_bg        \u001b[49m\n",
      "\u001b[1m  1    bold            \u001b[22m\n",
      "\u001b[2m  2    dim             \u001b[22m\n",
      "\u001b[3m  3    italic          \u001b[23m\n",
      "\u001b[4m  4    underline       \u001b[24m\n",
      "\u001b[5m  5    blink           \u001b[25m\n",
      "\u001b[7m  7    invert          \u001b[27m\n",
      "\u001b[8m  8    hidden          \u001b[28m\n",
      "\u001b[9m  9    strikethrough   \u001b[29m\n",
      "\u001b[22m 22    reset_bold      \n",
      "\u001b[22m 22    reset_dim       \n",
      "\u001b[23m 23    reset_italic    \n",
      "\u001b[24m 24    reset_underline \n",
      "\u001b[25m 25    reset_blink     \n",
      "\u001b[27m 27    reset_invert    \n",
      "\u001b[28m 28    reset_hidden    \n",
      "\u001b[29m 29    reset_strikethrough\n",
      "\u001b[0m  0    reset           \n"
     ]
    }
   ],
   "source": [
    "demo()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "df973d4e",
   "metadata": {},
   "source": [
    "# Export -"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ad32b076",
   "metadata": {},
   "outputs": [],
   "source": [
    "#|hide\n",
    "import nbdev; nbdev.nbdev_export()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "84fe290c",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "python3",
   "language": "python",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
